/**
* Copyright (c) By zengqh.
*
* This program is just for fun or demo, in the hope that it  
* will be useful, you can redistribute it and/or modify freely.
*
* Time: 2013/05/26
* File: enn_gl_render_system.cpp
* Blog: http://www.cnblogs.com/zengqh/
**/

#include "enn_gl_render_system.h"
#include "enn_gl_vb.h"
#include "enn_gl_ib.h"
#include "enn_gl_frame_buffer.h"
#include "enn_gl_texture_obj.h"
#include "enn_gl_render_buffer.h"
#include "enn_gl_program.h"

namespace enn
{
RenderSystem::RenderSystem()
: num_batches_rendered_(0)
, num_prim_rendered_(0)
, fps_(0.0f)
, curr_element_mask_(0)
, curr_vb_(0)
, curr_ib_(0)
, curr_eff_(0)
, curr_fb_(0)
, vb_data_(0)
, curr_active_tex_(-1)
, max_scratch_buffers_request_(0)
{
	for (int i = 0; i < MAX_TEXTURE_UNITS; ++i)
	{
		tex_list_[i] = 0;
	}

	for (int i = 0; i < RS_NUM; ++i)
	{
		curr_render_state_[i] = (uint32)(-1);
	}

	resetRenderState();
}

RenderSystem::~RenderSystem()
{
	shutdown();
}

bool RenderSystem::init()
{
#if ENN_PLATFORM == ENN_PLATFORM_WIN32
	GLeeInit();
#endif

	curr_fb_->createFB();
}

void RenderSystem::shutdown()
{
	ENN_SAFE_RELEASE(curr_fb_);
}

bool RenderSystem::beginFrame()
{
	num_batches_rendered_ = 0;
	num_prim_rendered_ = 0;
}

void RenderSystem::endFrame()
{
	
}

void RenderSystem::setVB(VertexBuffer* vb)
{
	if (vb)
	{
		GLuint handle = vb->getHandle();
		gl.glBindBuffer( GL_ARRAY_BUFFER, handle );
		
		if (handle)
		{
			vb_data_ = 0;
		}
		else
		{
			vb_data_ = vb->getData();
		}
	}
	else
	{
		gl.glBindBuffer( GL_ARRAY_BUFFER, 0 );
		vb_data_ = 0;
	}

	checkGLError();
}

void RenderSystem::setIB(IndexBuffer* ib)
{
	if (ib)
	{
		GLuint handle = ib->getHandle();
		gl.glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, handle );
	}
	else
	{
		gl.glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
	}

	checkGLError();
}

void RenderSystem::setTexture(int sampler, TextureObj* tex)
{
	activeTexture( sampler );

	GLuint handle = tex ? tex->getHandle() : 0;

	if ( handle )
	{
		glBindTexture( GL_TEXTURE_2D, handle );
	}
	else
	{
		glBindTexture( GL_TEXTURE_2D, 0 );
	}

	checkGLError();
}

void RenderSystem::setTextureCube(int sampler, TextureObj* tex)
{
	activeTexture( sampler );

	GLuint handle = tex ? tex->getHandle() : 0;

	if ( handle )
	{
		glBindTexture( GL_TEXTURE_CUBE_MAP, handle );
	}
	else
	{
		glBindTexture( GL_TEXTURE_CUBE_MAP, 0 );
	}
}

void RenderSystem::activeTexture(int sampler)
{
	if (sampler >= MAX_TEXTURE_UNITS)
	{
		ENN_ASSERT(0);
		return;
	}

	if (curr_active_tex_ != sampler)
	{
		gl.glActiveTexture( GL_TEXTURE0 + sampler );
		checkGLError();

		curr_active_tex_ = sampler;
	}
}

void RenderSystem::setProgram(Program* pro)
{
	if (curr_eff_ != pro)
	{
		GLuint handle = pro ? pro->getHandle() : 0;
		gl.glUseProgram( handle );

		curr_eff_ = pro;
	}
}

void RenderSystem::resetRenderState()
{
	setWireFrame(false);
	setFrontFace(true);
	setCullFace(true);
	enableCullFace(true);
	enableZTest(true);
	enableZWrite(true);
	enableBlend(false);
	setBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void RenderSystem::setWireFrame(bool b)
{
#if !defined(GL_ES_VERSION_2_0)
	if (curr_render_state_[RS_WIREFRAME] != static_cast<uint32>(b))
	{
		glPolygonMode( GL_FRONT_AND_BACK, b ? GL_LINE : GL_FILL );
		checkGLError();

		curr_render_state_[RS_WIREFRAME] = static_cast<uint32>(b);
	}
#endif
}

void RenderSystem::setFrontFace(bool is_ccw)
{
	if (curr_render_state_[RS_FRONTCCW] != static_cast<uint32>(is_ccw))
	{
		glFrontFace( is_ccw ? GL_CCW : GL_CW );
		checkGLError();

		curr_render_state_[RS_FRONTCCW] = static_cast<uint32>(is_ccw)
	}
	
}
void RenderSystem::setCullFace(bool is_back)
{
	if (curr_render_state_[RS_CULLFACE] != static_cast<uint32>(is_back))
	{
		glCullFace( is_back ? GL_BACK : GL_FRONT );
		checkGLError();

		curr_render_state_[RS_CULLFACE] = static_cast<uint32>(is_back)
	}
}

void RenderSystem::enableCullFace(bool en)
{
	if (curr_render_state_[RS_CULLFACEEN] != static_cast<uint32>(en))
	{
		enableCap(GL_CULL_FACE, en);
		checkGLError();

		curr_render_state_[RS_CULLFACEEN] = static_cast<uint32>(en)
	}
}

void RenderSystem::enableZTest(bool en)
{
	if (curr_render_state_[RS_ZTEST] != static_cast<uint32>(en))
	{
		enableCap(GL_DEPTH_TEST, en);
		checkGLError();

		curr_render_state_[RS_ZTEST] = static_cast<uint32>(en)
	}
}

void RenderSystem::enableZWrite(bool en)
{
	if (curr_render_state_[RS_ZWRITE] != static_cast<uint32>(en))
	{
		glDepthMask( en ? GL_TRUE : GL_FALSE );
		checkGLError();

		curr_render_state_[RS_ZWRITE] = static_cast<uint32>(en)
	}
}

void RenderSystem::enableBlend(bool en)
{
	if (curr_render_state_[RS_BLEND] != static_cast<uint32>(en))
	{
		enableCap(GL_BLEND, en);
		checkGLError();

		curr_render_state_[RS_BLEND] = static_cast<uint32>(en)
	}
}

void RenderSystem::setBlendFunc(GLenum sfactor, GLenum dfactor)
{
	if (blend_src_factor_ != sfactor || blend_dst_factor_ != dfactor)
	{
		glBlendFunc( sfactor, dfactor );
		checkGLError();

		blend_src_factor_ = sfactor;
		blend_dst_factor_ = dfactor;
	}
}

void RenderSystem::drawPrimitives(uint32 type, uint32 offset, uint32 num_vertices)
{
	if (type >= PRIM_NUM)
	{
		return;
	}

	if (!num_vertices)
	{
		return;
	}

	uint32 primitiveCount = 0;
	switch (type)
	{
	case RenderSystem::PRIM_LINELIST:
		primitiveCount = num_vertices / 2;
		glDrawArrays(GL_LINES, offset, num_vertices);
		break;
	case RenderSystem::PRIM_TRIANGLELIST:
		primitiveCount = num_vertices / 3;
		glDrawArrays(GL_TRIANGLES, offset, num_vertices);
		break;
	}

	num_prim_rendered_ += primitiveCount;
	num_batches_rendered_++;
}

void RenderSystem::drawIndexedPrimitives(uint32 type, uint32 start_idx, uint32 num_idx_count, , bool is_idx_16bit)
{
	if (type >= PRIM_NUM)
	{
		return;
	}

	if (!num_idx_count)
	{
		return;
	}

	if (!curr_ib_ || !curr_ib_->getHandle())
	{
		return;
	}

	uint32 primitiveCount = 0;

	GLenum mode;
	GLenum type;
	size_t bytes;
	int pn = 3;

	if ( type == PRIM_TRIANGLELIST )
	{
		mode = GL_TRIANGLES;
		primitiveCount = num_idx_count / 3;
		pn = 3;
	}
	else
	{
		mode = GL_LINES;
		primitiveCount = num_idx_count / 2;
		pn = 2;
	}

	if ( is_idx_16bit )
	{
		type  = GL_UNSIGNED_SHORT;
		bytes = sizeof(GLushort);
	}
	else
	{
		type  = GL_UNSIGNED_INT;
		bytes = sizeof(GLuint);
	}

	glDrawElements( mode, num_idx_count, type, (const void*)(bytes * start_idx) );
	
}

void RenderSystem::clear(uint32 flags, const Color& clr, float z)
{
	if ( flags & GL_COLOR_BUFFER_BIT )
	{
		glClearColor( clr.r_, clr.g_, clr.b_, clr.a_ );
	}

	if ( flags & GL_DEPTH_BUFFER_BIT )
	{
		gl.glClearDepth( z );
	}

	glClear( flags );
}

void* RenderSystem::reserveScratchBuffer(uint32 size)
{
	if (!size) return 0;

	if (size > max_scratch_buffers_request_)
	{
		max_scratch_buffers_request_ = size;
	}

	for (ScratchBufferList::iterator iter = scratch_buffers_.begin();
		iter != scratch_buffers_.end(); ++iter)
	{
		if (!iter->reserved_ && iter->size_ >= size)
		{
			iter->reserved_ = true;
			return iter->data_;
		}
	}

	for (ScratchBufferList::iterator iter = scratch_buffers_.begin();
		iter != scratch_buffers_.end(); ++iter)
	{
		if (!iter->reserved_)
		{
			ENN_SAFE_FREE(iter->data_);
			iter->data_ = ENN_MALLOC(size);
			iter->reserved_ = true;

			return iter->data_;
		}
	}

	ScratchBuffer newBuffer;
	newBuffer.data_ = ENN_MALLOC(size);
	newBuffer.size_ = size;
	newBuffer.reserved_ = true;
	scratch_buffers_.push_back(newBuffer);
}

void RenderSystem::freeScratchBuffer(void* buffer)
{
	if (!buffer)
	{
		return;
	}

	for (ScratchBufferList::iterator iter = scratch_buffers_.begin();
		iter != scratch_buffers_.end(); ++iter)
	{
		if (!iter->reserved_ && iter->data_ >= buffer)
		{
			iter->reserved_ = false;
			return;
		}
	}

	ENN_ASSERT(0);
}

}